<?php

namespace DreamCat\FrameCore\Factory\Impl\Controller;

use DreamCat\FrameCore\Exception\ConfigFatalError;
use DreamCat\FrameCore\Protocol\JsonApiProtocol;
use DreamCat\FrameInterface\ConfigReader;
use DreamCat\FrameInterface\Controller\ActionParamFactory;
use DreamCat\FrameInterface\Controller\ProtocolInterface;
use DreamCat\FrameInterface\Factory\ControllerFactory;
use DreamCat\FrameInterface\HttpHandle\FilterInterface;
use DreamCat\FrameInterface\HttpHandle\InterceptorInterface;
use FastRoute\Dispatcher;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionMethod;
use Zend\Diactoros\Response\TextResponse;

/**
 * 默认的Http请求处理器工厂
 * @author vijay
 */
class DefaultControllerFactory implements ControllerFactory
{
    /**
     * @Autowire
     * @var Dispatcher 路由派发器
     */
    private $dispatcher;
    /**
     * @Config http/notFoundCode
     * @var int $notFoundCode NOT_FOUND时返回的http状态码
     */
    private $notFoundCode = 404;
    /**
     * @Config http/notFoundMsg
     * @var string $notFoundMsg NOT_FOUND时返回的文本信息
     */
    private $notFoundMsg = "not found";
    /**
     * @Config http/notAllowedCode
     * @var int $notAllowedCode NOT_ALLOWED时返回的http状态码
     */
    private $notAllowedCode = 405;
    /**
     * @Config http/notAllowedMsg
     * @var string $notAllowedMsg NOT_ALLOWED时返回的文本信息
     */
    private $notAllowedMsg = "not allowed";
    /** @var FilterInterface[] $httpFilters 过滤器列表 */
    private $httpFilters;
    /** @var InterceptorInterface[] $interceptors 拦截器列表 */
    private $interceptors;
    /**
     * @Autowire
     * @var ActionParamFactory $actionParamFactory 参数生成工厂
     */
    private $actionParamFactory;
    /**
     * @Config protocol
     * @var array $protocolConfigs 协议配置
     */
    private $protocolConfigs = [];

    /**
     * @return Dispatcher 路由派发器
     */
    public function getDispatcher(): Dispatcher
    {
        return $this->dispatcher;
    }

    /**
     * @param Dispatcher $dispatcher 路由派发器
     * @return static 对象本身
     */
    public function setDispatcher(Dispatcher $dispatcher): DefaultControllerFactory
    {
        $this->dispatcher = $dispatcher;
        return $this;
    }

    /**
     * http请求处理入口
     * @param ContainerInterface $container 容器
     * @param ServerRequestInterface $request 请求对象
     * @return ResponseInterface 响应
     */
    public function httpHandle(ContainerInterface $container, ServerRequestInterface $request): ResponseInterface
    {
        # 调用所有过滤器，如果遇到有返回值则直接返回
        foreach ($this->getHttpFilters($container) as $filter) {
            $response = $filter->httpFilter($request);
            if ($response) {
                return $response;
            }
        }
        $routeInfo = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath());
        switch ($routeInfo[0]) {
            case Dispatcher::NOT_FOUND:
                return new TextResponse($this->getNotFoundMsg(), $this->getNotFoundCode());
            case Dispatcher::METHOD_NOT_ALLOWED:
                return new TextResponse($this->getNotAllowedMsg(), $this->getNotAllowedCode());
            case Dispatcher::FOUND:
                return $this->dealFound($container, $request, $routeInfo[1], $routeInfo[2] ?? []);
        }
        throw new \RuntimeException("路由派发器出现预期之外的返回值" . $routeInfo[0]);
    }

    /**
     * @param ContainerInterface $container 容器
     * @return FilterInterface[] 过滤器列表
     */
    protected function getHttpFilters(ContainerInterface $container): array
    {
        if ($this->httpFilters === null) {
            /** @var ConfigReader $configReader */
            $configReader = $container->get(ConfigReader::class);
            $filtersConfig = $configReader->get("filters", []);
            $this->httpFilters = [];
            foreach ($filtersConfig as $item) {
                if (is_subclass_of($item, FilterInterface::class)) {
                    $this->httpFilters[] = $container->get($item);
                }
            }
        }
        return $this->httpFilters;
    }

    /**
     * @return string NOT_FOUND时返回的文本信息
     */
    public function getNotFoundMsg(): string
    {
        return $this->notFoundMsg;
    }

    /**
     * @param string $notFoundMsg NOT_FOUND时返回的文本信息
     * @return static 对象本身
     */
    public function setNotFoundMsg(string $notFoundMsg): DefaultControllerFactory
    {
        $this->notFoundMsg = $notFoundMsg;
        return $this;
    }

    /**
     * @return int NOT_FOUND时返回的http状态码
     */
    public function getNotFoundCode(): int
    {
        return $this->notFoundCode;
    }

    /**
     * @param int $notFoundCode NOT_FOUND时返回的http状态码
     * @return static 对象本身
     */
    public function setNotFoundCode(int $notFoundCode): DefaultControllerFactory
    {
        $this->notFoundCode = $notFoundCode;
        return $this;
    }

    /**
     * @return string NOT_ALLOWED时返回的文本信息
     */
    public function getNotAllowedMsg(): string
    {
        return $this->notAllowedMsg;
    }

    /**
     * @param string $notAllowedMsg NOT_ALLOWED时返回的文本信息
     * @return static 对象本身
     */
    public function setNotAllowedMsg(string $notAllowedMsg): DefaultControllerFactory
    {
        $this->notAllowedMsg = $notAllowedMsg;
        return $this;
    }

    /**
     * @return int NOT_ALLOWED时返回的http状态码
     */
    public function getNotAllowedCode(): int
    {
        return $this->notAllowedCode;
    }

    /**
     * @param int $notAllowedCode NOT_ALLOWED时返回的http状态码
     * @return static 对象本身
     */
    public function setNotAllowedCode(int $notAllowedCode): DefaultControllerFactory
    {
        $this->notAllowedCode = $notAllowedCode;
        return $this;
    }

    /**
     * 处理找到handle的情况
     * @param ContainerInterface $container 容器
     * @param ServerRequestInterface $request http请求参数
     * @param string $handle 处理器字符串
     * @param array $pathVariable uri占位符
     * @return ResponseInterface 响应消息
     */
    protected function dealFound(
        ContainerInterface $container,
        ServerRequestInterface $request,
        string $handle,
        array $pathVariable
    ): ResponseInterface {
        list($ctlClassName, $actName) = $this->parseHandleStr($handle);
        if (!class_exists($ctlClassName) || !method_exists($ctlClassName, $actName)) {
            throw new ConfigFatalError("路由配置的handle字符串[{$handle}]不正确");
        }
        # 调用所有的拦截器，如果存在有返回值的则直接返回
        foreach ($this->getInterceptors($container) as $interceptor) {
            $response = $interceptor->httpInterceptor($ctlClassName, $actName, $request);
            if ($response) {
                return $response;
            }
        }
        # 调用协议转换输入
        $protocol = $this->getProtocol($ctlClassName, $actName, $container);
        $request = $protocol->convertInput($request);
        # 根据请求参数和控制器方法注释生成控制器方法
        /** @noinspection PhpUnhandledExceptionInspection */
        $params = $this->getActionParamFactory()
            ->createParams(new ReflectionMethod($ctlClassName, $actName), $request, $pathVariable);
        # 生成控制器，调用控制器方法
        $ctl = $container->get($ctlClassName);
        $response = call_user_func_array([
            $ctl,
            $actName,
        ], $params);
        # 调用协议转换输出
        if (!($response instanceof ResponseInterface)) {
            $response = $protocol->formatOutput($response);
        }
        return $protocol->convertOutput($response);
    }

    /**
     * 解析handle字符串
     * @param string $handleStr handle字符串
     * @return array 第一个元素是控制器名，第二个是方法名
     */
    protected function parseHandleStr(string $handleStr): array
    {
        $tmp = explode("::", $handleStr, 2);
        if (count($tmp) == 1) {
            $tmp[] = "index";
        }
        return $tmp;
    }

    /**
     * @param ContainerInterface $container 容器
     * @return InterceptorInterface[] 拦截器列表
     */
    protected function getInterceptors(ContainerInterface $container): array
    {
        if ($this->interceptors === null) {
            /** @var ConfigReader $configReader */
            $configReader = $container->get(ConfigReader::class);
            $interceptionsConfig = $configReader->get("interceptors", []);
            $this->interceptors = [];
            foreach ($interceptionsConfig as $item) {
                if (is_subclass_of($item, InterceptorInterface::class)) {
                    $this->interceptors[] = $container->get($item);
                }
            }
        }
        return $this->interceptors;
    }

    /**
     * 获取协议对象
     * @param string $className 控制器类名
     * @param string $actName 控制器方法名
     * @param ContainerInterface $container 容器对象
     * @return ProtocolInterface 协议对象
     */
    protected function getProtocol(string $className, string $actName, ContainerInterface $container): ProtocolInterface
    {
        $config = $this->getProtocolConfigs();
        if (isset($config["act"][$className][$actName])) {
            return $container->get($config["act"][$className][$actName]);
        } elseif (isset($config["ctl"][$className])) {
            return $container->get($config["ctl"][$className]);
        } elseif (isset($config["default"])) {
            return $container->get($config["default"]);
        } else {
            return $container->get(JsonApiProtocol::class);
        }
    }

    /**
     * @return array 协议配置
     */
    public function getProtocolConfigs(): array
    {
        return $this->protocolConfigs;
    }

    /**
     * @param array $protocolConfigs 协议配置
     * @return static 对象本身
     */
    public function setProtocolConfigs(array $protocolConfigs): DefaultControllerFactory
    {
        $this->protocolConfigs = $protocolConfigs;
        return $this;
    }

    /**
     * @return ActionParamFactory 参数生成工厂
     */
    public function getActionParamFactory(): ActionParamFactory
    {
        return $this->actionParamFactory;
    }

    /**
     * @param ActionParamFactory $actionParamFactory 参数生成工厂
     * @return static 对象本身
     */
    public function setActionParamFactory(ActionParamFactory $actionParamFactory): DefaultControllerFactory
    {
        $this->actionParamFactory = $actionParamFactory;
        return $this;
    }
}

# end of file
